using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using UnityEngine;

/// <summary>
/// Java archive file
/// </summary>
public class JarFile
{
    private string path;
    private static readonly Dictionary<string, ZipArchiveEntry> _classFileEntries = new();
    private static readonly Dictionary<string, ZipArchiveEntry> _reFileEntries = new();
    private static ZipArchive? _archive;

    public JarFile(string path)
    {
        this.path = path;
        // open jar as a zip archive and keep it opened
        _archive = ZipFile.OpenRead(path);

        // load only .class files
        foreach (var entry in _archive.Entries)
        {
            if (!entry.FullName.EndsWith(".class", StringComparison.OrdinalIgnoreCase))
            {
                var name = Path.ChangeExtension(entry.FullName, null);
                _reFileEntries[entry.FullName] = entry;
                continue;
            }

            var className = Path.ChangeExtension(entry.FullName, null);
            _classFileEntries[className] = entry;
        }
        ClassFiles = _classFileEntries.Keys.ToArray();
    }

    public static void DisposeArchive()
    {
        _archive?.Dispose();
    }

    public static ReadOnlySpan<byte> GetClass(string file)
    {
        string name = StartScript.Ins.AppDirectoryPath + "/" + JarFile.GetFileNameKey(file)+ ".class";
        if (File.Exists(name))
        {
            using Stream stream = File.OpenRead(name);
            using MemoryStream ms = new MemoryStream();
            stream.CopyTo(ms);
            return ms.ToArray();
        }
        else
        {
            return GetClassData(file);
        }
    }

    public static ReadOnlySpan<byte> GetClassData(string className)
    {
        Console.WriteLine("Load Class:" + className);
        if (!_classFileEntries.TryGetValue(className, out var entry))
            return null;
        lock (JavaVirtualMachine.Program._locker)
        {
            using var stream = entry.Open();
            using MemoryStream ms = new MemoryStream();
            stream.CopyTo(ms);
            byte[] data = ms.ToArray();
            return data;
        } 
    }

    public static byte[] GetReData(string name)
    {
        Console.WriteLine("Load Resource:" + name);
        if (!_reFileEntries.TryGetValue(name, out var entry))
            return null;
        lock (JavaVirtualMachine.Program._locker)
        {
            using var stream = entry.Open();
            using MemoryStream ms = new MemoryStream();
            stream.CopyTo(ms);
            return ms.ToArray();
        }
    }

    public static Stream GetReStream(string name)
    {
        name = GetFileNameKey(name);
        byte[] data = GetReData(name);
        if (data == null)
            return null;
        return new MemoryStream(data);
    }

    public static bool IsHaveFile(string path)
    {
        return _reFileEntries.ContainsKey(GetFileNameKey(path));
    }

    public static string GetFileNameKey(string path)
    {
        int length = StartScript.Ins.ResourcePath.Length;
        if (path.Length <= length)
            return path;
        if (path.Contains(StartScript.Ins.ResourcePath))
        {
            string s1 = path.Substring(length);
            string s2 = s1.Replace(@"\", "/");
            return s2;
        }
        return path;
    }

    /// <summary>
    /// JAR file manifest data
    /// </summary>
    public ManifestData Manifest { get; set; } = new();

    /// <summary>
    /// A list of class files (full relative path)
    /// </summary>
    public string[] ClassFiles { get; set; } = Array.Empty<string>();
}
